package com.gitee.wsl.net

import com.gitee.wsl.ext.base.currentTimeMillis
import com.gitee.wsl.net.base.CacheControlValue
import com.gitee.wsl.net.base.XAmzMetaBackoff
import com.gitee.wsl.net.base.XAmzMetaBestBefore
import com.gitee.wsl.net.base.XAmzMetaNextRefresh
import com.gitee.wsl.net.base.XBestBefore
import com.gitee.wsl.net.base.XMsMetaBestbefore
import com.gitee.wsl.net.base.XMsMetaNextrefresh
import com.gitee.wsl.net.base.XNextRefresh
import io.ktor.client.call.body
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.RedirectResponseException
import io.ktor.client.plugins.ResponseException
import io.ktor.client.plugins.ServerResponseException
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HeaderValue
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.cacheControl
import io.ktor.http.isSuccess
import io.ktor.http.parseHeaderValue
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.Job

fun HttpResponse.isWebPage(): Boolean {
    return headers["content-type"].orEmpty().contains("text/html")
}

suspend inline fun <reified T : Any> HttpResponse.bodyOrNull(
    statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK }
) = takeIf(statusFilter) ?.body<T>()

suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> {
    it.status != HttpStatusCode.NoContent
}

inline fun HttpResponse.throwOnUnsuccessful(
    unsuccessMessage: () -> String
) {
    if (status.isSuccess()) {
        return
    }
    throw ClientRequestException(this, unsuccessMessage())
}


/**
 * Throw a ResponseException or any of its subclasses if the status code is no good.
 */
suspend fun HttpResponse.throwIfNotSuccessful() {
    // as of HttpClientConfig<*>.addDefaultResponseValidation()
    when (status.value) {
        in 200..299 -> return
        in 300..399 -> throw RedirectResponseException(this, bodyAsTextOrElse())
        in 400..499 -> throw ClientRequestException(this, bodyAsTextOrElse())
        in 500..599 -> throw ServerResponseException(this, bodyAsTextOrElse())
        else -> throw ResponseException(this, bodyAsTextOrElse())
    }
}

suspend fun HttpResponse.bodyAsTextOrElse(): String {
    return runCatching { bodyAsText() }.getOrElse { "<body failed decoding>" }
}

internal fun HttpResponse.complete() {
    val job = coroutineContext[Job]!! as CompletableJob
    job.complete()
}

fun HttpResponse.isCacheable(): Boolean {
    if (!status.isSuccess()) return false

    val requestCacheControl = parseHeaderValue(call.request.headers[HttpHeaders.CacheControl])
    if (CacheControlValue.NO_STORE in requestCacheControl) return false

    val responseCacheControl = parseHeaderValue(headers[HttpHeaders.CacheControl])
    if (CacheControlValue.NO_CACHE in responseCacheControl) return false
    if (CacheControlValue.NO_STORE in responseCacheControl) return false

    return true
}


/**
 * Get the date of this response.
 * @return Unix timestamp in millis.
 */
fun HttpResponse.serverDate(): Long? {
    return headers[HttpHeaders.Date]?.let { it.toHttpDate()?.timestamp }
}

/** Used for testing only. */
private var offset: Long = 0

@Suppress("NOTHING_TO_INLINE")
internal inline fun now() = currentTimeMillis + offset

/** Used for testing only. */
internal fun skipTime(millis: Long) {
    offset += millis
}

/**
 * Get the expiry date of this response.
 * @return Unix timestamp in millis.
 */
fun HttpResponse.expiresDate(): Long? {
    return headers[HttpHeaders.XBestBefore]?.let { it.toHttpDate()?.timestamp }
        ?: headers[HttpHeaders.XAmzMetaBestBefore]?.let { it.toHttpDate()?.timestamp }
        ?: headers[HttpHeaders.XMsMetaBestbefore]?.let { it.toHttpDate()?.timestamp }
        ?: headers[HttpHeaders.Expires]?.let { it.toHttpDate()?.timestamp }
        ?: cacheControl().maxAge()?.takeIf { it > 0 }?.plus(now())
}

/**
 * Get the next refresh date of this response.
 * @return Unix timestamp in millis.
 */
fun HttpResponse.nextRefreshDate(): Long? {
    return headers[HttpHeaders.XNextRefresh]?.let { it.toHttpDate()?.timestamp }
        ?: headers[HttpHeaders.XAmzMetaNextRefresh]?.let { it.toHttpDate()?.timestamp }
        ?: headers[HttpHeaders.XMsMetaNextrefresh]?.let { it.toHttpDate()?.timestamp }
}

/**
 * Get the backoff in millis of this response.
 * @return backoff in millis.
 */
fun HttpResponse.backoff(): Long? {
    return headers[HttpHeaders.XAmzMetaBackoff]?.toLongOrNull()?.let { it * 1000L }
}

/**
 * Get the Cache-Control max-age value.
 * @return max-age in millis.
 */
fun List<HeaderValue>.maxAge(): Long? {
    val maxAgeKey = "max-age"
    return firstOrNull { it.value.startsWith(maxAgeKey) }
        ?.value
        ?.split("=")
        ?.getOrNull(1)
        ?.toLongOrNull()
        ?.times(1000L)
}


